using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Reflection;
using ZoneFiveSoftware.Common.Data.Fitness;
using ZoneFiveSoftware.Common.Data.Measurement;
using ZoneFiveSoftware.Common.Visuals;
using FilteredStatistics.Common;
using FilteredStatistics.Controller;
using FilteredStatistics.Data;
using FilteredStatistics.View;
using ZoneFiveSoftware.Common.Data;

namespace FilteredStatistics.Common.Controller
{
    class FilteredStatisticsStatisticsProvider : IStatisticsProvider
    {
        private class FilteredStatisticsColumnInfo
        {
            public FilteredStatisticsColumnInfo(String headerText, Color columnColor, String valueMember)
            {
                m_HeaderText = headerText;
                m_ColumnColor = columnColor;
                m_ValueMember = valueMember;
            }

            public String m_HeaderText;
            public Color m_ColumnColor;
            public String m_ValueMember;
        }

        public FilteredStatisticsStatisticsProvider()
        {
            FillColumnInfos();

            PluginMain.GetApplication().SystemPreferences.PropertyChanged += SystemPreferences_PropertyChanged;
        }

        void SystemPreferences_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "UICulture")
            {
                FillColumnInfos();
            }
        }

        public static FilteredStatisticsStatisticsProvider Instance
        {
            get
            {
                if (m_Instance == null)
                {
                    m_Instance = new FilteredStatisticsStatisticsProvider();
                }

                return m_Instance;
            }
        }

#region IStatisticsProvider members

        public IList<Guid> ColumnIds
        {
            get
            {                
                List<Guid> columns = new List<Guid>();

                columns.AddRange(m_ColumnInfos.Keys);

                return columns;
            }
        }

        public String GetColumnHeader(Guid columnId, IActivity activity)
        {
            // Activity sensitive text needs to be updated
            if (activity != null)
            {
                if (m_LastSpeedUnit != activity.Category.SpeedUnits)
                {
                    if (activity.Category.SpeedUnits == Speed.Units.Pace)
                    {
                        m_ColumnInfos[new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7")].m_HeaderText = PluginView.GetLocalizedString("MinPaceText");
                        m_ColumnInfos[new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6")].m_HeaderText = CommonResources.Text.LabelAvgPace;
                        m_ColumnInfos[new Guid("1b5165e8-db42-428d-94ad-be18f19c6273")].m_HeaderText = PluginView.GetLocalizedString("MaxPaceText");
                        m_ColumnInfos[new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b")].m_HeaderText = CommonResources.Text.LabelPaceVsAverage;
                        m_ColumnInfos[new Guid("732c5b05-4ce7-4e6a-91fa-c8d21aa21ae2")].m_HeaderText = PluginView.GetLocalizedString("PaceStandardDeviation");
                    }
                    else
                    {
                        m_ColumnInfos[new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7")].m_HeaderText = PluginView.GetLocalizedString("MinSpeedText");
                        m_ColumnInfos[new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6")].m_HeaderText = CommonResources.Text.LabelAvgSpeed;
                        m_ColumnInfos[new Guid("1b5165e8-db42-428d-94ad-be18f19c6273")].m_HeaderText = PluginView.GetLocalizedString("MaxSpeedText");
                        m_ColumnInfos[new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b")].m_HeaderText = CommonResources.Text.LabelSpeedVsAverage;
                        m_ColumnInfos[new Guid("732c5b05-4ce7-4e6a-91fa-c8d21aa21ae2")].m_HeaderText = PluginView.GetLocalizedString("SpeedStandardDeviation");
                    }

                    m_LastSpeedUnit = activity.Category.SpeedUnits;
                }
            }
            else if (m_LastSpeedUnit != Speed.Units.Speed)
            {
                m_ColumnInfos[new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7")].m_HeaderText = PluginView.GetLocalizedString("MinSpeedText");
                m_ColumnInfos[new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6")].m_HeaderText = CommonResources.Text.LabelAvgSpeed;
                m_ColumnInfos[new Guid("1b5165e8-db42-428d-94ad-be18f19c6273")].m_HeaderText = PluginView.GetLocalizedString("MaxSpeedText");
                m_ColumnInfos[new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b")].m_HeaderText = CommonResources.Text.LabelSpeedVsAverage;
                m_ColumnInfos[new Guid("732c5b05-4ce7-4e6a-91fa-c8d21aa21ae2")].m_HeaderText = PluginView.GetLocalizedString("SpeedStandardDeviation");
                m_LastSpeedUnit = Speed.Units.Speed;
            }

            if (activity != null)
            {
                if (m_LastElevationUnit != activity.Category.ElevationUnits)
                {
                    String temp = PluginView.GetLocalizedString("VerticalSpeedText");
                    temp += "(" + Length.LabelAbbr(activity.Category.ElevationUnits) + "/" + CommonResources.Text.LabelHour_lower + ")";
                    m_ColumnInfos[new Guid("344c4d17-da2e-4b79-b2e2-d7362d74feaa")].m_HeaderText = temp;
                    m_LastElevationUnit = activity.Category.ElevationUnits;
                }
            }
            else if (m_LastElevationUnit != Length.Units.NauticalMile)
            {
                m_ColumnInfos[new Guid("344c4d17-da2e-4b79-b2e2-d7362d74feaa")].m_HeaderText = PluginView.GetLocalizedString("VerticalSpeedText");
                m_LastElevationUnit = Length.Units.NauticalMile; // NauticalMile is used to represent "no unit" since it can't be set in the settings
            }

            return m_ColumnInfos[columnId].m_HeaderText;
        }

        public Color GetColumnColor(Guid columnId)
        {
            return m_ColumnInfos[columnId].m_ColumnColor;
        }

        public String GetStatisticLabel(Guid columnId, object currentRange)
        {
            MethodInfo getLabelMethod = GetType().GetMethod("Get" + GetValueMemberFromGuid(columnId), new Type[] { typeof(ActivityRangeInfoCache) });

            Debug.Assert(getLabelMethod != null && getLabelMethod.ReturnType == typeof(String));

            return getLabelMethod.Invoke(this, new object[] { currentRange }) as String;
        }

        public String GetTotalStatisticLabel(Guid columnId, IList<object> allRanges)
        {
            MethodInfo getLabelMethod = GetType().GetMethod("Get" + GetValueMemberFromGuid(columnId), new Type[] { typeof(List<ActivityRangeInfoCache>) });

            Debug.Assert(getLabelMethod != null && getLabelMethod.ReturnType == typeof(String));

            List<ActivityRangeInfoCache> allWrappers = new List<ActivityRangeInfoCache>();

            foreach (object range in allRanges)
            {
                allWrappers.Add(range as ActivityRangeInfoCache);
            }

            return getLabelMethod.Invoke(this, new object[] { allWrappers }) as String;
        }

        public int CompareRanges(Guid columnId, int sortingOrder, object range1, object range2)
        {
            TreeListColumnInfo.ColumnSortingType sortOrder = (TreeListColumnInfo.ColumnSortingType)sortingOrder;
            String sortMember = GetValueMemberFromGuid(columnId);
            MethodInfo sortMemberMethod = GetType().GetMethod("Get" + sortMember, new Type[] { typeof(ActivityRangeInfoCache) });

            Debug.Assert(sortMemberMethod != null && sortMemberMethod.ReturnType == typeof(String));

            if (sortMemberMethod.Invoke(this, new object[] { range1 }) as String == String.Empty)
            {
                // Revert to default value, we don't have this info
                sortMember = "StartTime";
                sortOrder = TreeListColumnInfo.ColumnSortingType.Ascending;
            }

            MethodInfo valueMethod = GetType().GetMethod("Get" + sortMember + "Value", new Type[] { typeof(ActivityRangeInfoCache) });

            Debug.Assert(valueMethod != null);

            double valueX;
            double valueY;
            double result;

            // Special case for time spans
            if (sortMember == "StartTime" ||
                sortMember == "EndTime" ||
                sortMember == "ElapsedTime")
            {
                Debug.Assert(valueMethod.ReturnType == typeof(TimeSpan));

                TimeSpan tempValueX = (TimeSpan)valueMethod.Invoke(this, new object[] { range1 });
                TimeSpan tempValueY = (TimeSpan)valueMethod.Invoke(this, new object[] { range2 });

                valueX = tempValueX.TotalSeconds;
                valueY = tempValueY.TotalSeconds;
            }
            else
            {
                valueX = (double)valueMethod.Invoke(this, new object[] { range1 });
                valueY = (double)valueMethod.Invoke(this, new object[] { range2 });
            }

            if (sortOrder == TreeListColumnInfo.ColumnSortingType.Ascending)
            {
                result = valueX - valueY;
            }
            else
            {
                result = valueY - valueX;
            }

            return Math.Sign(result);
        }

#endregion

#region Single range stats

        public TimeSpan GetStartTimeValue(ActivityRangeInfoCache range)
        {
            return range.PauselessRange.Lower - range.ActivityInfo.ActualTrackStart;
        }

        public String GetStartTime(ActivityRangeInfoCache range)
        {
            TimeSpan startTime = GetStartTimeValue(range);

            return String.Format("{0:00}:{1:00}:{2:00}", startTime.Hours, startTime.Minutes, startTime.Seconds);
        }

        public TimeSpan GetEndTimeValue(ActivityRangeInfoCache range)
        {
            return range.PauselessRange.Upper - range.ActivityInfo.ActualTrackStart;
        }

        public String GetEndTime(ActivityRangeInfoCache range)
        {
            TimeSpan endTime = GetEndTimeValue(range);

            return String.Format("{0:00}:{1:00}:{2:00}", endTime.Hours, endTime.Minutes, endTime.Seconds);
        }

        public double GetStartDistanceValue(ActivityRangeInfoCache range)
        {
            return Length.Convert(range.PauselessRangeDistanceTrack.Min, Length.Units.Meter, range.Activity.Category.DistanceUnits);
        }

        public String GetStartDistance(ActivityRangeInfoCache range)
        {
            return String.Format("{0:0.00} {1}", GetStartDistanceValue(range), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
        }

        public double GetEndDistanceValue(ActivityRangeInfoCache range)
        {
            return Length.Convert(range.PauselessRangeDistanceTrack.Max, Length.Units.Meter, range.Activity.Category.DistanceUnits);
        }

        public String GetEndDistance(ActivityRangeInfoCache range)
        {
            return String.Format("{0:0.00} {1}", GetEndDistanceValue(range), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
        }

        public TimeSpan GetElapsedTimeValue(ActivityRangeInfoCache range)
        {
            return range.PauselessRange.Upper - range.PauselessRange.Lower;
        }

        public String GetElapsedTime(ActivityRangeInfoCache range)
        {
            TimeSpan elapsedTime = GetElapsedTimeValue(range);

            return String.Format("{0:00}:{1:00}:{2:00}", elapsedTime.Hours, elapsedTime.Minutes, elapsedTime.Seconds);
        }

        public double GetDistanceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeDistanceTrack.Max - range.PauselessRangeDistanceTrack.Min, Length.Units.Meter, range.Activity.Category.DistanceUnits);
            }

            return 0;
        }

        public String GetDistance(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", GetDistanceValue(range), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetTimePercentValue(ActivityRangeInfoCache range)
        {
            if (Utils.GetActiveActivityTime(range.Activity).TotalMilliseconds != 0)
            {
                TimeSpan elapsedTime = range.PauselessRange.Upper - range.PauselessRange.Lower;
                return elapsedTime.TotalSeconds / range.ActivityInfo.Time.TotalSeconds;
            }

            return 0;
        }

        public String GetTimePercent(ActivityRangeInfoCache range)
        {
            if (range.ActivityInfo.Time.TotalMilliseconds != 0)
            {
                return String.Format("{0:0.00} %", GetTimePercentValue(range) * 100.0);
            }

            return String.Empty;
        }

        public double GetDistancePercentValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Max > 0)
            {
                double rangeDistance = range.PauselessRangeDistanceTrack.Max - range.PauselessRangeDistanceTrack.Min;

                return rangeDistance / range.ActivityPauselessDistanceTrack.Max;
            }

            return 0;
        }

        public String GetDistancePercent(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Max > 0 && range.PauselessRangeDistanceTrack.Max != range.PauselessRangeDistanceTrack.Min)
            {
                return String.Format("{0:0.00} %", GetDistancePercentValue(range) * 100.0);
            }

            return String.Empty;
        }

        public double GetElevationChangeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return GetEndElevationValue(range) - GetStartElevationValue(range);
            }

            return 0;
        }

        public String GetElevationChange(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetElevationChangeValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{2}{0:0.0} {1}", Math.Abs(value), Length.LabelAbbr(range.Activity.Category.ElevationUnits), sign);
            }

            return String.Empty;
        }

        public double GetStartElevationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeElevationTrack[0].Value, Length.Units.Meter, range.Activity.Category.ElevationUnits);
            }

            return 0;
        }

        public String GetStartElevation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                String sign = String.Empty;

                if (GetStartElevationValue(range) < 0)
                {
                    sign = "-";
                }

                return String.Format("{2}{0:0.0} {1}", Math.Abs(Math.Round(GetStartElevationValue(range), 1)), Length.LabelAbbr(range.Activity.Category.ElevationUnits), sign);
            }

            return String.Empty;
        }

        public double GetEndElevationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeElevationTrack[range.PauselessRangeElevationTrack.Count - 1].Value, Length.Units.Meter, range.Activity.Category.ElevationUnits);
            }

            return 0;
        }

        public String GetEndElevation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                String sign = String.Empty;

                if (GetEndElevationValue(range) < 0)
                {
                    sign = "-";
                }

                return String.Format("{2}{0:0.0} {1}", Math.Abs(Math.Round(GetEndElevationValue(range), 1)), Length.LabelAbbr(range.Activity.Category.ElevationUnits), sign);
            }

            return String.Empty;
        }

        public double GetTotalAscentValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo;
                double totalAscent = 0;

                if (range.ContainsStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")))
                {
                    categoryInfo = range.RetrieveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")) as ZoneCategoryInfo;
                }
                else
                {
                    categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);

                    // Remove totals row
                    categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                    range.SaveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c"), categoryInfo);
                }

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.Low > 0)
                    {
                        totalAscent += zoneInfo.ElevationChangeMeters;
                    }
                }

                return Length.Convert(totalAscent, Length.Units.Meter, range.Activity.Category.ElevationUnits);
            }

            return 0;
        }

        public String GetTotalAscent(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0} {1}", Math.Round(GetTotalAscentValue(range), 1), Length.LabelAbbr(range.Activity.Category.ElevationUnits));
            }

            return String.Empty;
        }

        public double GetTotalDescentValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo;
                double totalDescent = 0;

                if (range.ContainsStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")))
                {
                    categoryInfo = range.RetrieveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")) as ZoneCategoryInfo;
                }
                else
                {
                    categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);

                    // Remove totals row
                    categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                    range.SaveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c"), categoryInfo);
                }

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.High < 0)
                    {
                        totalDescent += zoneInfo.ElevationChangeMeters;
                    }
                }

                return Math.Abs(Length.Convert(totalDescent, Length.Units.Meter, range.Activity.Category.ElevationUnits));
            }

            return 0;
        }

        public String GetTotalDescent(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0} {1}", Math.Round(GetTotalDescentValue(range), 1), Length.LabelAbbr(range.Activity.Category.ElevationUnits));
            }

            return String.Empty;
        }

        public double GetAscentDistanceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo;
                double totalAscentDistance = 0;

                if (range.ContainsStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")))
                {
                    categoryInfo = range.RetrieveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")) as ZoneCategoryInfo;
                }
                else
                {
                    categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);

                    // Remove totals row
                    categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                    range.SaveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c"), categoryInfo);
                }

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.Low > 0)
                    {
                        totalAscentDistance += zoneInfo.TotalDistanceMeters;
                    }
                }

                return Length.Convert(totalAscentDistance, Length.Units.Meter, range.Activity.Category.DistanceUnits);
            }

            return 0;
        }

        public String GetAscentDistance(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetAscentDistanceValue(range), 2), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetFlatDistanceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo;
                double totalFlatDistance = 0;

                if (range.ContainsStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")))
                {
                    categoryInfo = range.RetrieveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")) as ZoneCategoryInfo;
                }
                else
                {
                    categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);

                    // Remove totals row
                    categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                    range.SaveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c"), categoryInfo);
                }

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.Low < 0 && zoneInfo.Zone.High > 0)
                    {
                        totalFlatDistance += zoneInfo.TotalDistanceMeters;
                    }
                }

                return Length.Convert(totalFlatDistance, Length.Units.Meter, range.Activity.Category.DistanceUnits);
            }

            return 0;
        }

        public String GetFlatDistance(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetFlatDistanceValue(range), 2), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetDescentDistanceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                ZoneCategoryInfo categoryInfo;
                double totalDescentDistance = 0;

                if (range.ContainsStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")))
                {
                    categoryInfo = range.RetrieveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c")) as ZoneCategoryInfo;
                }
                else
                {
                    categoryInfo = ZoneCategoryInfo.Calculate(range.ActivityInfo, PluginMain.GetApplication().DisplayOptions.SelectedClimbZone, range.RangeWithPausesGradeTrack, 100.0f);

                    // Remove totals row
                    categoryInfo.Zones.RemoveAt(categoryInfo.Zones.Count - 1);

                    range.SaveStoredInfo(new Guid("f6aa3844-25d0-4170-b81f-3268ce5f655c"), categoryInfo);
                }

                foreach (ZoneInfo zoneInfo in categoryInfo.Zones)
                {
                    if (zoneInfo.Zone.High < 0)
                    {
                        totalDescentDistance += zoneInfo.TotalDistanceMeters;
                    }
                }

                return Length.Convert(totalDescentDistance, Length.Units.Meter, range.Activity.Category.DistanceUnits);
            }

            return 0;
        }

        public String GetDescentDistance(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetDescentDistanceValue(range), 2), Length.LabelAbbr(range.Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetMinPaceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                // Use max since pace is inverse of speed
                return Utils.SpeedToPace(Length.Convert(range.PauselessRangeSpeedTrack.Min, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);
            }

            return 0;
        }

        public double GetMinSpeedValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeSpeedTrack.Min, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public String GetMinSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double paceValue = GetMinPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetMinSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public String GetMinAltSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits != Speed.Units.Pace)
                {
                    double paceValue = GetMinPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetMinSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public double GetAvgPaceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeDistanceTrack.Count > 0)
            {
                double distanceInUnit = GetDistanceValue(range);
                TimeSpan timeDuration = GetElapsedTimeValue(range);

                if (distanceInUnit > 0)
                {
                    return timeDuration.TotalMinutes / distanceInUnit;
                }
            }

            return 0;
        }

        public double GetAvgSpeedValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public String GetAvgSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double paceValue = GetAvgPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetAvgSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public String GetAvgAltSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits != Speed.Units.Pace)
                {
                    double paceValue = GetAvgPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetAvgSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public double GetMaxSpeedValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                return Length.Convert(range.PauselessRangeSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public double GetMaxPaceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                // Use min since pace is inverse of speed
                return Utils.SpeedToPace(Length.Convert(range.PauselessRangeSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);
            }

            return 0;
        }

        public String GetMaxSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double paceValue = GetMaxPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetMaxSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public String GetMaxAltSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits != Speed.Units.Pace)
                {
                    double paceValue = GetMaxPaceValue(range);
                    int minuteValue = (int)paceValue;
                    double secondValue = Math.Round((paceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetMaxSpeedValue(range));
                }
            }

            return String.Empty;
        }

        public double GetPaceVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                double activityAvgPace = Utils.SpeedToPace(Length.Convert(range.ActivityPauselessSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);

                return GetAvgPaceValue(range) - activityAvgPace;
            }

            return 0;
        }

        public double GetSpeedVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                double speedVsAvg = range.PauselessRangeSpeedTrack.Avg - range.ActivityPauselessSpeedTrack.Avg;

                return Length.Convert(speedVsAvg, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public String GetSpeedVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                String sign = String.Empty;
                String valueText = String.Empty;
                double value = 0;

                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    value = GetPaceVsAvgValue(range);

                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    valueText = String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    value = GetSpeedVsAvgValue(range);
                    valueText = String.Format("{0:0.0}", Math.Abs(value));
                }

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{0}{1}", sign, valueText);
            }

            return String.Empty;
        }

        public String GetAltSpeedVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                String sign = String.Empty;
                String valueText = String.Empty;
                double value = 0;

                if (range.Activity.Category.SpeedUnits != Speed.Units.Pace)
                {
                    value = GetPaceVsAvgValue(range);

                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    valueText = String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    value = GetSpeedVsAvgValue(range);
                    valueText = String.Format("{0:0.0}", Math.Abs(value));
                }

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{0}{1}", sign, valueText);
            }

            return String.Empty;
        }

        public double GetPaceStandardDeviationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                NumericTimeDataSeries paceTrack;

                if (range.ContainsStoredInfo(new Guid("b8d0e22e-695d-486e-8b41-50840ca3c5fc")))
                {
                    // Used saved data to prevent recalculation
                    paceTrack = range.RetrieveStoredInfo(new Guid("b8d0e22e-695d-486e-8b41-50840ca3c5fc")) as NumericTimeDataSeries;
                }
                else
                {
                    paceTrack = new NumericTimeDataSeries();

                    // Convert all speeds to pace
                    foreach (ITimeValueEntry<float> speedValue in range.PauselessRangeSpeedTrack)
                    {
                        paceTrack.Add(range.PauselessRangeSpeedTrack.StartTime + new TimeSpan(0, 0, (int)speedValue.ElapsedSeconds),
                                      (float)Utils.SpeedToPace(Length.Convert(speedValue.Value, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour));
                    }

                    // Save for later
                    range.SaveStoredInfo(new Guid("b8d0e22e-695d-486e-8b41-50840ca3c5fc"), paceTrack);
                }

                return Utils.GetStandardDeviation(paceTrack);
            }

            return 0;
        }

        public double GetSpeedStandardDeviationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                double stdDev = Utils.GetStandardDeviation(range.PauselessRangeSpeedTrack);

                return Length.Convert(stdDev, Length.Units.Meter, Utils.MajorLengthUnit(range.Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
            }

            return 0;
        }

        public String GetSpeedStandardDeviation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double value = GetPaceStandardDeviationValue(range);

                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    return String.Format("{0:0.0}", GetSpeedStandardDeviationValue(range));
                }
            }

            return String.Empty;
        }

        public String GetAltSpeedStandardDeviation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeSpeedTrack.Count > 0)
            {
                if (range.Activity.Category.SpeedUnits != Speed.Units.Pace)
                {
                    double value = GetPaceStandardDeviationValue(range);

                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    return String.Format("{0:0.0}", GetSpeedStandardDeviationValue(range));
                }
            }

            return String.Empty;
        }

        public double GetVerticalSpeedValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return GetElevationChangeValue(range) / GetElapsedTimeValue(range).TotalHours;
            }

            return 0;
        }

        public String GetVerticalSpeed(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetVerticalSpeedValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

        public double GetMinCadenceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return range.PauselessRangeCadenceTrack.Min;
            }

            return 0;
        }

        public String GetMinCadence(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetMinCadenceValue(range));
            }

            return String.Empty;
        }

        public double GetAvgCadenceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return range.PauselessRangeCadenceTrack.Avg;
            }

            return 0;
        }

        public String GetAvgCadence(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetAvgCadenceValue(range));
            }

            return String.Empty;
        }

        public double GetMaxCadenceValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return range.PauselessRangeCadenceTrack.Max;
            }

            return 0;
        }
        public String GetMaxCadence(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetMaxCadenceValue(range));
            }

            return String.Empty;
        }

        public double GetCadenceVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return range.PauselessRangeCadenceTrack.Avg - range.ActivityPauselessCadenceTrack.Avg;
            }

            return 0;
        }
        public String GetCadenceVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetCadenceVsAvgValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

        public double GetCadenceStandardDeviationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return Utils.GetStandardDeviation(range.PauselessRangeCadenceTrack);
            }

            return 0;
        }

        public String GetCadenceStandardDeviation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetCadenceStandardDeviationValue(range));
            }

            return String.Empty;
        }

        public double GetMinHRValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return range.PauselessRangeHeartRateTrack.Min;
            }

            return 0;
        }

        public String GetMinHR(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(range.ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", GetMinHRValue(range) / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, GetMinHRValue(range) - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0}{1}", GetMinHRValue(range), percentMax);
            }

            return String.Empty;
        }

        public double GetAvgHRValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return range.PauselessRangeHeartRateTrack.Avg;
            }

            return 0;
        }

        public String GetAvgHR(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(range.ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", GetAvgHRValue(range) / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, GetAvgHRValue(range) - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0.0}{1}", GetAvgHRValue(range), percentMax);
            }

            return String.Empty;
        }

        public double GetMaxHRValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return range.PauselessRangeHeartRateTrack.Max;
            }

            return 0;
        }

        public String GetMaxHR(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(range.ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", GetMaxHRValue(range) / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, GetMaxHRValue(range) - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0}{1}", GetMaxHRValue(range), percentMax);
            }

            return String.Empty;
        }

        public double GetHRVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return range.PauselessRangeHeartRateTrack.Avg - range.ActivityPauselessHeartRateTrack.Avg;
            }

            return 0;
        }

        public String GetHRVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetHRVsAvgValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

        public double GetHRStandardDeviationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return Utils.GetStandardDeviation(range.PauselessRangeHeartRateTrack);
            }

            return 0;
        }

        public String GetHRStandardDeviation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeHeartRateTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetHRStandardDeviationValue(range));
            }

            return String.Empty;
        }

        public double GetMinGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return range.PauselessRangeGradeTrack.Min * 100.0f;
            }

            return 0;
        }

        public String GetMinGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", GetMinGradeValue(range));
            }

            return String.Empty;
        }

        public double GetAvgGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                double elevationChangeValue = Length.Convert(GetElevationChangeValue(range), range.Activity.Category.ElevationUnits, range.Activity.Category.DistanceUnits);

                return elevationChangeValue / GetDistanceValue(range) * 100;
            }

            return 0;
        }

        public String GetAvgGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", GetAvgGradeValue(range));
            }

            return String.Empty;
        }

        public double GetAvgAscentGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0 && GetAscentDistanceValue(range) > 0)
            {
                double ascentMeters = Length.Convert(GetTotalAscentValue(range), range.Activity.Category.ElevationUnits, Length.Units.Meter);
                double ascentDistanceMeters = Length.Convert(GetAscentDistanceValue(range), range.Activity.Category.DistanceUnits, Length.Units.Meter);

                return (ascentMeters * 100) / ascentDistanceMeters;
            }

            return 0;
        }

        public String GetAvgAscentGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", Math.Round(GetAvgAscentGradeValue(range), 1));
            }

            return String.Empty;
        }

        public double GetAvgDescentGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0 && GetDescentDistanceValue(range) > 0)
            {
                double descentMeters = Length.Convert(GetTotalDescentValue(range), range.Activity.Category.ElevationUnits, Length.Units.Meter);
                double descentDistanceMeters = Length.Convert(GetDescentDistanceValue(range), range.Activity.Category.DistanceUnits, Length.Units.Meter);

                return (descentMeters * 100) / descentDistanceMeters;
            }

            return 0;
        }

        public String GetAvgDescentGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", Math.Round(GetAvgDescentGradeValue(range), 1));
            }

            return String.Empty;
        }

        public double GetMaxGradeValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return range.PauselessRangeGradeTrack.Max * 100.0f;
            }

            return 0;
        }

        public String GetMaxGrade(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", GetMaxGradeValue(range));
            }

            return String.Empty;
        }

        public double GetGradeVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return (range.PauselessRangeGradeTrack.Avg - range.ActivityPauselessGradeTrack.Avg) * 100.0f;
            }

            return 0;
        }

        public String GetGradeVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetGradeVsAvgValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }
        
        public double GetGradeStandardDeviationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return Utils.GetStandardDeviation(range.PauselessRangeGradeTrack) * 100.0f;
            }

            return 0;
        }

        public String GetGradeStandardDeviation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangeGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetGradeStandardDeviationValue(range));
            }

            return String.Empty;
        }

        public double GetMinPowerValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return range.PauselessRangePowerTrack.Min;
            }

            return 0;
        }

        public String GetMinPower(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetMinPowerValue(range));
            }

            return String.Empty;
        }

        public double GetAvgPowerValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return range.PauselessRangePowerTrack.Avg;
            }

            return 0;
        }

        public String GetAvgPower(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetAvgPowerValue(range));
            }

            return String.Empty;
        }

        public double GetMaxPowerValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return range.PauselessRangePowerTrack.Max;
            }

            return 0;
        }

        public String GetMaxPower(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetMaxPowerValue(range));
            }

            return String.Empty;
        }

        public double GetPowerVsAvgValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return range.PauselessRangePowerTrack.Avg - range.ActivityPauselessPowerTrack.Avg;
            }

            return 0;
        }

        public String GetPowerVsAvg(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                String sign = String.Empty;
                double value = GetPowerVsAvgValue(range);

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(value), sign);
            }

            return String.Empty;
        }

        public double GetPowerStandardDeviationValue(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return Utils.GetStandardDeviation(range.PauselessRangePowerTrack);
            }

            return 0;
        }

        public String GetPowerStandardDeviation(ActivityRangeInfoCache range)
        {
            if (range.PauselessRangePowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetPowerStandardDeviationValue(range));
            }

            return String.Empty;
        }

#endregion

#region All ranges stats

        public String GetStartTime(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetEndTime(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetStartDistance(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetEndDistance(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetElapsedTime(List<ActivityRangeInfoCache> allRanges)
        {
            TimeSpan elapsedTime = new TimeSpan(0, 0, 0);

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                elapsedTime += GetElapsedTimeValue(range);
            }

            return String.Format("{0:00}:{1:00}:{2:00}", elapsedTime.Hours, elapsedTime.Minutes, elapsedTime.Seconds);
        }

        public String GetDistance(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.MovingDistanceMetersTrack.Count > 0)
            {
                double distance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    distance += GetDistanceValue(range);
                }

                return String.Format("{0:0.00} {1}", distance, Length.LabelAbbr(allRanges[0].Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetTimePercentValue(List<ActivityRangeInfoCache> allRanges)
        {
                double totalTimePercent = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalTimePercent += GetTimePercentValue(range);
                }

                return totalTimePercent;
        }

        public String GetTimePercent(List<ActivityRangeInfoCache> allRanges)
        {
            if (GetTimePercentValue(allRanges) != 0)
            {
                return String.Format("{0:0.00} %", GetTimePercentValue(allRanges) * 100.0);
            }

            return String.Empty;
        }

        public double GetDistancePercentValue(List<ActivityRangeInfoCache> allRanges)
        {
            double totalDistancePercent = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                totalDistancePercent += GetDistancePercentValue(range);
            }

            return totalDistancePercent;
        }

        public String GetDistancePercent(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.MovingDistanceMetersTrack.Max > 0)
            {
                return String.Format("{0:0.00} %", GetDistancePercentValue(allRanges) * 100.0);
            }

            return String.Empty;
        }

        public String GetStartElevation(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetEndElevation(List<ActivityRangeInfoCache> allRanges)
        {
            return String.Empty;
        }

        public String GetElevationChange(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double elevationChange = 0;
                String sign = String.Empty;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    elevationChange += GetElevationChangeValue(range);
                }

                if (elevationChange < 0)
                {
                    sign = "-";
                }
                else if (elevationChange > 0)
                {
                    sign = "+";
                }

                return String.Format("{2}{0:0.0} {1}", Math.Abs(elevationChange), Length.LabelAbbr(allRanges[0].Activity.Category.ElevationUnits), sign);
            }

            return String.Empty;
        }


        public double GetTotalAscentValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalAscent = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalAscent += GetTotalAscentValue(range);
                }

                return totalAscent;
            }

            return 0;
        }

        public String GetTotalAscent(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0} {1}", Math.Round(GetTotalAscentValue(allRanges), 1), Length.LabelAbbr(allRanges[0].Activity.Category.ElevationUnits));
            }

            return String.Empty;
        }

        public double GetTotalDescentValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDescent = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDescent += GetTotalDescentValue(range);
                }

                return totalDescent;
            }

            return 0;
        }

        public String GetTotalDescent(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0} {1}", Math.Round(GetTotalDescentValue(allRanges), 1), Length.LabelAbbr(allRanges[0].Activity.Category.ElevationUnits));
            }

            return String.Empty;
        }

        public double GetAscentDistanceValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDistance += GetAscentDistanceValue(range);
                }

                return totalDistance;
            }

            return 0;
        }

        public String GetAscentDistance(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetAscentDistanceValue(allRanges), 2), Length.LabelAbbr(allRanges[0].Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetFlatDistanceValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDistance += GetFlatDistanceValue(range);
                }

                return totalDistance;
            }

            return 0;
        }

        public String GetFlatDistance(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetFlatDistanceValue(allRanges), 2), Length.LabelAbbr(allRanges[0].Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public double GetDescentDistanceValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDistance += GetDescentDistanceValue(range);
                }

                return totalDistance;
            }

            return 0;
        }

        public String GetDescentDistance(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.00} {1}", Math.Round(GetDescentDistanceValue(allRanges), 2), Length.LabelAbbr(allRanges[0].Activity.Category.DistanceUnits));
            }

            return String.Empty;
        }

        public String GetMinSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                bool isPace = allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace;
                double min = Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;

                if (isPace)
                {
                    min = 0;
                }

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    if (isPace)
                    {
                        // Use max since pace is inverse of speed
                        min = Math.Max(min, GetMinPaceValue(range));
                    }
                    else
                    {
                        min = Math.Min(min, GetMinSpeedValue(range));
                    }
                }

                if (isPace)
                {
                    int minuteValue = (int)min;
                    double secondValue = Math.Round((min - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", min);
                }
            }

            return String.Empty;
        }

        public String GetMinAltSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                bool isPace = allRanges[0].Activity.Category.SpeedUnits != Speed.Units.Pace;
                double min = Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;

                if (isPace)
                {
                    min = 0;
                }

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    if (isPace)
                    {
                        // Use max since pace is inverse of speed
                        min = Math.Max(min, GetMinPaceValue(range));
                    }
                    else
                    {
                        min = Math.Min(min, GetMinSpeedValue(range));
                    }
                }

                if (isPace)
                {
                    int minuteValue = (int)min;
                    double secondValue = Math.Round((min - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", min);
                }
            }

            return String.Empty;
        }

        public double GetAvgPaceValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;
            double distancePercent = GetDistancePercentValue(allRanges);

            if (distancePercent > 0)
            {
                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    avg += GetAvgPaceValue(range) * GetDistancePercentValue(range);
                }

                return avg / distancePercent;
            }

            return 0;
        }

        public double GetAvgSpeedValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgSpeedValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetAvgSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                if (allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    double avgPaceValue = GetAvgPaceValue(allRanges);
                    int minuteValue = (int)avgPaceValue;
                    double secondValue = Math.Round((avgPaceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetAvgSpeedValue(allRanges));
                }
            }

            return String.Empty;
        }

        public String GetAvgAltSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                if (allRanges[0].Activity.Category.SpeedUnits != Speed.Units.Pace)
                {
                    double avgPaceValue = GetAvgPaceValue(allRanges);
                    int minuteValue = (int)avgPaceValue;
                    double secondValue = Math.Round((avgPaceValue - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", GetAvgSpeedValue(allRanges));
                }
            }

            return String.Empty;
        }

        public String GetMaxSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                bool isPace = allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace;
                double max = 0;

                if (isPace)
                {
                    max = Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
                }

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    if (isPace)
                    {
                        // Use min since pace is inverse of speed
                        max = Math.Min(max, GetMaxPaceValue(range));
                    }
                    else
                    {
                        max = Math.Max(max, GetMaxSpeedValue(range));
                    }
                }

                if (isPace)
                {
                    int minuteValue = (int)max;
                    double secondValue = Math.Round((max - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", max);
                }
            }

            return String.Empty;
        }

        public String GetMaxAltSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                bool isPace = allRanges[0].Activity.Category.SpeedUnits != Speed.Units.Pace;
                double max = 0;

                if (isPace)
                {
                    max = Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Max, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;
                }

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    if (isPace)
                    {
                        // Use min since pace is inverse of speed
                        max = Math.Min(max, GetMaxPaceValue(range));
                    }
                    else
                    {
                        max = Math.Max(max, GetMaxSpeedValue(range));
                    }
                }

                if (isPace)
                {
                    int minuteValue = (int)max;
                    double secondValue = Math.Round((max - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", minuteValue, secondValue);
                }
                else
                {
                    return String.Format("{0:0.0}", max);
                }
            }

            return String.Empty;
        }

        public String GetSpeedVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                double value;
                String valueText;

                if (allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    value = GetAvgPaceValue(allRanges) - Utils.SpeedToPace(Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);
                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    valueText = String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    value = GetAvgSpeedValue(allRanges) - Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;

                    valueText = String.Format("{0:0.0}", Math.Abs(value));
                }

                String sign = String.Empty;

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{0}{1}", sign, valueText);
            }

            return String.Empty;
        }

        public String GetAltSpeedVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                double value;
                String valueText;

                if (allRanges[0].Activity.Category.SpeedUnits != Speed.Units.Pace)
                {
                    value = GetAvgPaceValue(allRanges) - Utils.SpeedToPace(Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour);
                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    valueText = String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    value = GetAvgSpeedValue(allRanges) - Length.Convert(allRanges[0].ActivityPauselessSpeedTrack.Avg, Length.Units.Meter, Utils.MajorLengthUnit(allRanges[0].Activity.Category.DistanceUnits)) * Constants.SecondsPerHour;

                    valueText = String.Format("{0:0.0}", Math.Abs(value));
                }

                String sign = String.Empty;

                if (value < 0)
                {
                    sign = "-";
                }
                else if (value > 0)
                {
                    sign = "+";
                }

                return String.Format("{0}{1}", sign, valueText);
            }

            return String.Empty;
        }

        public double GetPaceStandardDeviationValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;
            int sizes = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                sizes += range.PauselessRangeSpeedTrack.Count - 1;
                avg += Math.Pow(GetPaceStandardDeviationValue(range), 2.0f) * (range.PauselessRangeSpeedTrack.Count - 1);
            }

            return Math.Sqrt(avg / sizes);
        }

        public double GetSpeedStandardDeviationValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;
            int sizes = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                sizes += range.PauselessRangeSpeedTrack.Count - 1;
                avg += Math.Pow(GetSpeedStandardDeviationValue(range), 2.0f) * (range.PauselessRangeSpeedTrack.Count - 1);
            }

            return Math.Sqrt(avg / sizes);
        }

        public String GetSpeedStandardDeviation(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                double value;

                if (allRanges[0].Activity.Category.SpeedUnits == Speed.Units.Pace)
                {
                    value = GetPaceStandardDeviationValue(allRanges);
                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    return String.Format("{0:0.0}", GetSpeedStandardDeviationValue(allRanges));
                }
            }

            return String.Empty;
        }

        public String GetAltSpeedStandardDeviation(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessSpeedTrack.Count > 0)
            {
                double value;

                if (allRanges[0].Activity.Category.SpeedUnits != Speed.Units.Pace)
                {
                    value = GetPaceStandardDeviationValue(allRanges);
                    int minuteValue = (int)value;
                    double secondValue = Math.Round((value - minuteValue) * Constants.SecondsPerMinute, 2);

                    return String.Format("{0:00}:{1:00.00}", Math.Abs(minuteValue), Math.Abs(secondValue));
                }
                else
                {
                    return String.Format("{0:0.0}", GetSpeedStandardDeviationValue(allRanges));
                }
            }

            return String.Empty;
        }

        public double GetVerticalSpeedValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetVerticalSpeedValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetVerticalSpeed(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessElevationTrack.Count > 0)
            {
                String sign = String.Empty;

                if (GetVerticalSpeedValue(allRanges) < 0)
                {
                    sign = "-";
                }
                else if (GetVerticalSpeedValue(allRanges) > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(GetVerticalSpeedValue(allRanges)), sign);
            }

            return String.Empty;
        }

        public String GetMinCadence(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                double min = allRanges[0].ActivityPauselessCadenceTrack.Max;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    min = Math.Min(min, GetMinCadenceValue(range));
                }

                return String.Format("{0:0.0}", min);
            }

            return String.Empty;
        }

        public double GetAvgCadenceValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgCadenceValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetAvgCadence(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetAvgCadenceValue(allRanges));
            }

            return String.Empty;
        }

        public String GetMaxCadence(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                double max = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    max = Math.Max(max, GetMaxCadenceValue(range));
                }

                return String.Format("{0:0.0}", max);
            }

            return String.Empty;
        }

        public String GetCadenceVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                double cadenceVsAvg = GetAvgCadenceValue(allRanges) - allRanges[0].ActivityPauselessCadenceTrack.Avg;
                String sign = String.Empty;

                if (cadenceVsAvg < 0)
                {
                    sign = "-";
                }
                else if (cadenceVsAvg > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(cadenceVsAvg), sign);
            }

            return String.Empty;
        }

        public double GetCadenceStandardDeviationValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;
            int sizes = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                sizes += range.PauselessRangeCadenceTrack.Count - 1;
                avg += Math.Pow(GetCadenceStandardDeviationValue(range), 2.0f) * (range.PauselessRangeCadenceTrack.Count - 1);
            }

            return Math.Sqrt(avg / sizes);
        }

        public String GetCadenceStandardDeviation(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessCadenceTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetCadenceStandardDeviationValue(allRanges));
            }

            return String.Empty;
        }

        public String GetMinHR(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                double min = allRanges[0].ActivityPauselessHeartRateTrack.Max;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    min = Math.Min(min, GetMinHRValue(range));
                }

                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(allRanges[0].ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", min / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, min - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0}{1}", min, percentMax);
            }

            return String.Empty;
        }

        public double GetAvgHRValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgHRValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetAvgHR(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                double avg = GetAvgHRValue(allRanges);
                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(allRanges[0].ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", avg / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, avg - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0.0}{1}", avg, percentMax);
            }

            return String.Empty;
        }

        public String GetMaxHR(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                double max = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    max = Math.Max(max, GetMaxHRValue(range));
                }

                String percentMax = String.Empty;
                IAthleteInfoEntry lastAthleteEntry = PluginMain.GetApplication().Logbook.Athlete.InfoEntries.LastEntryAsOfDate(allRanges[0].ActivityInfo.ActualTrackStart);

                // Value is in BPM so convert to the % max HR if we have the info
                if (!float.IsNaN(lastAthleteEntry.MaximumHeartRatePerMinute))
                {
                    if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfMax)
                    {
                        percentMax = String.Format(" ({0:0}%)", max / lastAthleteEntry.MaximumHeartRatePerMinute * 100);
                    }
                    else if (PluginMain.GetApplication().SystemPreferences.AnalysisSettings.HeartRatePercentType == HeartRate.PercentTypes.PercentOfReserve &&
                             !float.IsNaN(lastAthleteEntry.RestingHeartRatePerMinute))
                    {
                        double reserveAmount = lastAthleteEntry.MaximumHeartRatePerMinute - lastAthleteEntry.RestingHeartRatePerMinute;
                        double reserveValue = Math.Max(0, max - lastAthleteEntry.RestingHeartRatePerMinute);

                        if (reserveAmount > 0)
                        {
                            percentMax = String.Format(" ({0:0}%)", reserveValue / reserveAmount * 100);
                        }
                    }
                }

                return String.Format("{0:0}{1}", max, percentMax);
            }

            return String.Empty;
        }

        public String GetHRVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                double HRVsAvg = GetAvgHRValue(allRanges) - allRanges[0].ActivityPauselessHeartRateTrack.Avg;
                String sign = String.Empty;

                if (HRVsAvg < 0)
                {
                    sign = "-";
                }
                else if (HRVsAvg > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(HRVsAvg), sign);
            }

            return String.Empty;
        }

        public double GetHRStandardDeviationValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;
            int sizes = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                sizes += range.PauselessRangeHeartRateTrack.Count - 1;
                avg += Math.Pow(GetHRStandardDeviationValue(range), 2.0f) * (range.PauselessRangeHeartRateTrack.Count - 1);
            }

            return Math.Sqrt(avg / sizes);
        }

        public String GetHRStandardDeviation(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessHeartRateTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetHRStandardDeviationValue(allRanges));
            }

            return String.Empty;
        }

        public String GetMinGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                double min = allRanges[0].ActivityPauselessGradeTrack.Max * 100;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    min = Math.Min(min, GetMinGradeValue(range));
                }

                return String.Format("{0:0.0}%", min);
            }

            return String.Empty;
        }

        public double GetAvgGradeValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgGradeValue(range) * GetDistancePercentValue(range);
            }

            return avg / GetDistancePercentValue(allRanges);
        }

        public String GetAvgGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", GetAvgGradeValue(allRanges));
            }

            return String.Empty;
        }

        public double GetAvgAscentGradeValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalAscent = 0;
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalAscent += GetTotalAscentValue(range);
                    totalDistance += GetAscentDistanceValue(range);
                }

                if (totalDistance > 0)
                {
                    return (totalAscent * 100) / Length.Convert(totalDistance, allRanges[0].Activity.Category.DistanceUnits, allRanges[0].Activity.Category.ElevationUnits);
                }
            }

            return 0;
        }

        public String GetAvgAscentGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", Math.Round(GetAvgAscentGradeValue(allRanges), 1));
            }

            return String.Empty;
        }

        public double GetAvgDescentGradeValue(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                double totalDescent = 0;
                double totalDistance = 0;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    totalDescent += GetTotalDescentValue(range);
                    totalDistance += GetDescentDistanceValue(range);
                }

                if (totalDistance > 0)
                {
                    return (totalDescent * 100) / Length.Convert(totalDistance, allRanges[0].Activity.Category.DistanceUnits, allRanges[0].Activity.Category.ElevationUnits);
                }
            }

            return 0;
        }

        public String GetAvgDescentGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityInfo.SmoothedElevationTrack.Count > 0)
            {
                return String.Format("{0:0.0}%", Math.Round(GetAvgDescentGradeValue(allRanges), 1));
            }

            return String.Empty;
        }

        public String GetMaxGrade(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                double max = allRanges[0].ActivityPauselessGradeTrack.Min * 100.0f;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    max = Math.Max(max, GetMaxGradeValue(range));
                }

                return String.Format("{0:0.0}%", max);
            }

            return String.Empty;
        }

        public String GetGradeVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                double gradeVsAvg = GetAvgGradeValue(allRanges) - (allRanges[0].ActivityPauselessGradeTrack.Avg * 100.0f);
                String sign = String.Empty;

                if (gradeVsAvg < 0)
                {
                    sign = "-";
                }
                else if (gradeVsAvg > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(gradeVsAvg), sign);
            }

            return String.Empty;
        }

        public double GetGradeStandardDeviationValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;
            int sizes = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                sizes += range.PauselessRangeGradeTrack.Count - 1;
                avg += Math.Pow(GetGradeStandardDeviationValue(range), 2.0f) * (range.PauselessRangeGradeTrack.Count - 1);
            }

            return Math.Sqrt(avg / sizes);
        }

        public String GetGradeStandardDeviation(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessGradeTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetGradeStandardDeviationValue(allRanges));
            }

            return String.Empty;
        }

        public String GetMinPower(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                double min = allRanges[0].ActivityPauselessPowerTrack.Max;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    min = Math.Min(min, GetMinPowerValue(range));
                }

                return String.Format("{0:0.0}", min);
            }

            return String.Empty;
        }

        public double GetAvgPowerValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                avg += GetAvgPowerValue(range) * GetTimePercentValue(range);
            }

            return avg / GetTimePercentValue(allRanges);
        }

        public String GetAvgPower(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetAvgPowerValue(allRanges));
            }

            return String.Empty;
        }

        public String GetMaxPower(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                double max = allRanges[0].ActivityPauselessPowerTrack.Min;

                foreach (ActivityRangeInfoCache range in allRanges)
                {
                    max = Math.Max(max, GetMaxPowerValue(range));
                }

                return String.Format("{0:0.0}", max);
            }

            return String.Empty;
        }

        public String GetPowerVsAvg(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                double powerVsAvg = GetAvgPowerValue(allRanges) - allRanges[0].ActivityPauselessPowerTrack.Avg;
                String sign = String.Empty;

                if (powerVsAvg < 0)
                {
                    sign = "-";
                }
                else if (powerVsAvg > 0)
                {
                    sign = "+";
                }

                return String.Format("{1}{0:0.0}", Math.Abs(powerVsAvg), sign);
            }

            return String.Empty;
        }

        public double GetPowerStandardDeviationValue(List<ActivityRangeInfoCache> allRanges)
        {
            double avg = 0;
            int sizes = 0;

            foreach (ActivityRangeInfoCache range in allRanges)
            {
                sizes += range.PauselessRangePowerTrack.Count - 1;
                avg += Math.Pow(GetPowerStandardDeviationValue(range), 2.0f) * (range.PauselessRangePowerTrack.Count - 1);
            }

            return Math.Sqrt(avg / sizes);
        }

        public String GetPowerStandardDeviation(List<ActivityRangeInfoCache> allRanges)
        {
            if (allRanges[0].ActivityPauselessPowerTrack.Count > 0)
            {
                return String.Format("{0:0.0}", GetPowerStandardDeviationValue(allRanges));
            }

            return String.Empty;
        }

#endregion

        private void FillColumnInfos()
        {
            m_ColumnInfos = new Dictionary<Guid,FilteredStatisticsColumnInfo>();

            m_ColumnInfos.Add(new Guid("31c2df34-39a5-4496-8adc-2674caee1b76"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelStartTime,
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "StartTime"));
            m_ColumnInfos.Add(new Guid("fb860869-29ff-4ae5-b748-8b68c6d6b362"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelEndTime,
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "EndTime"));
            m_ColumnInfos.Add(new Guid("dd64616e-c5d4-487d-935a-935d4f8a7c01"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelTime,
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "ElapsedTime"));
            m_ColumnInfos.Add(new Guid("ab3da546-8363-4e07-9e05-630b2d4e77df"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("StartDistanceText"),
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "StartDistance")); 
            m_ColumnInfos.Add(new Guid("94a7f1dc-fb2e-47c0-a8bc-fc33684f64b4"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("EndDistanceText"),
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "EndDistance"));
            m_ColumnInfos.Add(new Guid("9fee353d-5695-4ef8-9d40-5b8684b83a97"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelDistance,
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "Distance"));
            m_ColumnInfos.Add(new Guid("6b21c28d-45d3-4129-9c31-a360593e7449"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("TimePercentText"),
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "TimePercent"));
            m_ColumnInfos.Add(new Guid("0a38f45f-78db-470c-a258-49a5bfc47d16"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("DistancePercentText"),
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "DistancePercent"));
            m_ColumnInfos.Add(new Guid("4729b986-a86b-4c4b-a280-b28c62773c22"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("StartElevationText"),
                                                               Constants.LineChartTypesColors[1],
                                                               "StartElevation"));
            m_ColumnInfos.Add(new Guid("63844936-c14e-44b5-81f8-b16ed67b9560"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("EndElevationText"),
                                                               Constants.LineChartTypesColors[1],
                                                               "EndElevation"));
            m_ColumnInfos.Add(new Guid("1b06055e-c232-4383-9d43-50e558a69af1"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelElevationChange,
                                                               Constants.LineChartTypesColors[1],
                                                               "ElevationChange"));
            m_ColumnInfos.Add(new Guid("30145d7d-67c1-4093-b461-312db69d8d1f"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("TotalAscentText"),
                                                               Constants.LineChartTypesColors[1],
                                                               "TotalAscent"));
            m_ColumnInfos.Add(new Guid("ec36c785-0a0b-4162-a8f8-66807bab182b"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("TotalDescentText"),
                                                               Constants.LineChartTypesColors[1],
                                                               "TotalDescent"));
            m_ColumnInfos.Add(new Guid("9ca8d4ff-7373-4c82-a731-99a4bda0805b"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("AscentDistanceText"),
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "AscentDistance"));
            m_ColumnInfos.Add(new Guid("36f52593-cc7b-4249-b3fb-1378980745bc"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("FlatDistanceText"),
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "FlatDistance"));
            m_ColumnInfos.Add(new Guid("defc80b5-faee-42d2-b645-4ade3a1670d6"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("DescentDistanceText"),
                                                               PluginMain.GetApplication().VisualTheme.ControlText,
                                                               "DescentDistance"));
            m_ColumnInfos.Add(new Guid("7efdb968-97ba-4fef-9db1-bb3e7e1486e7"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MinSpeedText"),
                                                               Constants.LineChartTypesColors[5],
                                                               "MinSpeed"));
            m_ColumnInfos.Add(new Guid("76e38cb6-b1e9-46e6-9e95-6f529af9f0e6"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelAvgSpeed,
                                                               Constants.LineChartTypesColors[5],
                                                               "AvgSpeed"));
            m_ColumnInfos.Add(new Guid("1b5165e8-db42-428d-94ad-be18f19c6273"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MaxSpeedText"),
                                                               Constants.LineChartTypesColors[5],
                                                               "MaxSpeed"));
            m_ColumnInfos.Add(new Guid("0c26ece5-b299-4a46-8a52-6e0f1c89fb8b"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelSpeedVsAverage,
                                                               Constants.LineChartTypesColors[5],
                                                               "SpeedVsAvg"));
            m_ColumnInfos.Add(new Guid("732c5b05-4ce7-4e6a-91fa-c8d21aa21ae2"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("SpeedStandardDeviation"),
                                                               Constants.LineChartTypesColors[5],
                                                               "SpeedStandardDeviation"));
            m_ColumnInfos.Add(new Guid("be3ce2f9-3113-4289-81ba-e51414372970"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MinAltSpeedText"),
                                                               Constants.LineChartTypesColors[5],
                                                               "MinAltSpeed"));
            m_ColumnInfos.Add(new Guid("648d43da-9b6c-4e65-8f50-f871fe057352"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("AverageAltSpeed"),
                                                               Constants.LineChartTypesColors[5],
                                                               "AvgAltSpeed"));
            m_ColumnInfos.Add(new Guid("c0207599-3864-43b2-9de5-af3d15c3b830"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MaxAltSpeedText"),
                                                               Constants.LineChartTypesColors[5],
                                                               "MaxAltSpeed"));
            m_ColumnInfos.Add(new Guid("97e2c205-f88d-4a67-833f-e53e713239a0"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("AltSpeedVsAverage"),
                                                               Constants.LineChartTypesColors[5],
                                                               "AltSpeedVsAvg"));
            m_ColumnInfos.Add(new Guid("621e01ab-3cf0-4201-a80d-8b92f8b30037"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("AltSpeedStandardDeviation"),
                                                               Constants.LineChartTypesColors[5],
                                                               "AltSpeedStandardDeviation"));
            m_ColumnInfos.Add(new Guid("344c4d17-da2e-4b79-b2e2-d7362d74feaa"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("VerticalSpeedText"),
                                                               Constants.LineChartTypesColors[5],
                                                               "VerticalSpeed"));
            m_ColumnInfos.Add(new Guid("72a37849-3d55-457b-8cc1-a87206104261"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MinCadenceText"),
                                                               Constants.LineChartTypesColors[0],
                                                               "MinCadence"));
            m_ColumnInfos.Add(new Guid("a5eb53c6-3b4d-4b3e-884f-065d0b97d873"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelAvgCadence,
                                                               Constants.LineChartTypesColors[0],
                                                               "AvgCadence"));
            m_ColumnInfos.Add(new Guid("4f37ed62-395d-4744-9d3f-6b4a28f6aa4e"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelMaxCadence,
                                                               Constants.LineChartTypesColors[0],
                                                               "MaxCadence"));
            m_ColumnInfos.Add(new Guid("75800fd9-a35a-4f89-84c2-afcab9c906fa"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("CadenceVsAvgText"),
                                                               Constants.LineChartTypesColors[0],
                                                               "CadenceVsAvg"));
            m_ColumnInfos.Add(new Guid("d703e245-0836-41f5-9174-6f94a2674a25"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("CadenceStandardDeviation"),
                                                               Constants.LineChartTypesColors[0],
                                                               "CadenceStandardDeviation"));
            m_ColumnInfos.Add(new Guid("dda8fcc6-3af6-4cf5-8a6e-b89a8ea12a2e"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MinHRText"),
                                                               Constants.LineChartTypesColors[3],
                                                               "MinHR"));
            m_ColumnInfos.Add(new Guid("a5b8217a-f593-4d6f-8901-16aa2c33ade0"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelAvgHR,
                                                               Constants.LineChartTypesColors[3],
                                                               "AvgHR"));
            m_ColumnInfos.Add(new Guid("fa1ab8ef-9dd3-4c17-97e1-2e64d8678cea"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelMaxHR,
                                                               Constants.LineChartTypesColors[3],
                                                               "MaxHR"));
            m_ColumnInfos.Add(new Guid("bf0555f8-ec23-47e1-b03a-c73ff26780fb"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("HRVsAvgText"),
                                                               Constants.LineChartTypesColors[3],
                                                               "HRVsAvg"));
            m_ColumnInfos.Add(new Guid("6fad6439-c271-4808-aa23-d44b33fd4c68"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("HRStandardDeviation"),
                                                               Constants.LineChartTypesColors[3],
                                                               "HRStandardDeviation"));
            m_ColumnInfos.Add(new Guid("d3187244-d571-4e8d-9d58-c3a16b9a98cf"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MinPowerText"),
                                                               Constants.LineChartTypesColors[4],
                                                               "MinPower")); 
            m_ColumnInfos.Add(new Guid("550e262d-2563-4131-9f37-04ceda1a8097"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("AvgPowerText"),
                                                               Constants.LineChartTypesColors[4],
                                                               "AvgPower"));
            m_ColumnInfos.Add(new Guid("62aa7fca-e9b4-4ae9-8411-e01511ac55a7"),
                              new FilteredStatisticsColumnInfo(CommonResources.Text.LabelMaxPower,
                                                               Constants.LineChartTypesColors[4],
                                                               "MaxPower"));
            m_ColumnInfos.Add(new Guid("76695911-807c-44cc-ac7a-3367184db00e"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("PowerVsAvgText"),
                                                               Constants.LineChartTypesColors[4],
                                                               "PowerVsAvg"));
            m_ColumnInfos.Add(new Guid("e85d2337-b707-40d8-a417-718dff068219"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("PowerStandardDeviation"),
                                                               Constants.LineChartTypesColors[4],
                                                               "PowerStandardDeviation"));
            m_ColumnInfos.Add(new Guid("c2c92402-2c1c-4339-b5bd-981842d8cb8f"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MinGradeText"),
                                                               Constants.LineChartTypesColors[2],
                                                               "MinGrade"));
            m_ColumnInfos.Add(new Guid("57521cfe-edf2-4bc0-a654-7ea79872c8ab"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("AvgGradeText"),
                                                               Constants.LineChartTypesColors[2],
                                                               "AvgGrade"));
            m_ColumnInfos.Add(new Guid("3210bbff-605b-4978-abca-4f336a2268b0"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("AvgAscentGradeText"),
                                                               Constants.LineChartTypesColors[2],
                                                               "AvgAscentGrade"));
            m_ColumnInfos.Add(new Guid("c92dee39-2170-44df-8d21-9eddd816bd3b"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("AvgDescentGradeText"),
                                                               Constants.LineChartTypesColors[2],
                                                               "AvgDescentGrade"));
            m_ColumnInfos.Add(new Guid("d029aa4b-1d65-4033-b57c-d1dce27b573a"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("MaxGradeText"),
                                                               Constants.LineChartTypesColors[2],
                                                               "MaxGrade"));
            m_ColumnInfos.Add(new Guid("b4b2ff41-507f-4d0e-85c7-94d5f135a147"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("GradeVsAvgText"),
                                                               Constants.LineChartTypesColors[2],
                                                               "GradeVsAvg"));
            m_ColumnInfos.Add(new Guid("16746777-1ba3-4f8b-91f1-1dc7be331377"),
                              new FilteredStatisticsColumnInfo(PluginView.GetLocalizedString("GradeStandardDeviation"),
                                                               Constants.LineChartTypesColors[2],
                                                               "GradeStandardDeviation"));

            m_LastSpeedUnit = Speed.Units.Speed;
            m_LastElevationUnit = Length.Units.NauticalMile; // NauticalMile is used to represent "no unit" since it can't be set in the settings

            if (m_ValueMemberToGuidTable == null)
            {
                Dictionary<Guid, FilteredStatisticsColumnInfo>.Enumerator enumerator = m_ColumnInfos.GetEnumerator();

                m_ValueMemberToGuidTable = new Dictionary<string,Guid>();
                while(enumerator.MoveNext())
                {
                    m_ValueMemberToGuidTable.Add(enumerator.Current.Value.m_ValueMember, enumerator.Current.Key);
                }
            }
        }

        private String GetValueMemberFromGuid(Guid id)
        {
            return m_ColumnInfos[id].m_ValueMember;
        }

        public Guid GetGuidFromValueMember(String valueMember)
        {
            return m_ValueMemberToGuidTable[valueMember];
        }

        private static FilteredStatisticsStatisticsProvider m_Instance = null;

        private Dictionary<Guid, FilteredStatisticsColumnInfo> m_ColumnInfos;
        private Dictionary<String, Guid> m_ValueMemberToGuidTable;
        private Speed.Units m_LastSpeedUnit;
        private Length.Units m_LastElevationUnit;
    }
}